package com.jwt.demo.util;

import cn.hutool.core.util.ObjectUtil;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.jwt.demo.config.UserLoginPermission;
import com.jwt.demo.db.bean.UserRole;
import com.jwt.demo.db.mapper.UserRoleMapper;
import com.jwt.demo.model.TokenLoginInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;
import java.util.Date;

/**
 * token 工具类
 * @author chenlirun
 * @date 2021/7/8 17:27
 */
@Component
public class UserLoginTokenUtil {

    /**
     * 查询用户访问权限
     * @author chenlirun
     * @date 2021/7/18 17:36
     */
    @Autowired
    private UserRoleMapper userRoleMapper;

    // 静态实例化类对象
    private static UserLoginTokenUtil userLoginTokenUtil;

    /**
     * @PostConstruct
     * 1、首先这个注解是由Java提供的，它用来修饰一个非静态的void方法。它会在服务器加载Servlet的时候运行，并且只运行一次。
     * 2、用于处理普通工具类中方法无法被 static修饰，加入这个注解之后就可以在类初始化之前执行这个注解下的方法。
     * 3、我这个就是工具类中无法注入 mapper
     */
    @PostConstruct
    public void init(){
        userLoginTokenUtil=this;
    }


    //设置 token 过期时间为 30 分钟
    public static final long EXPIRE_TIME=30*60*1000;

    /**
     * 生成 token 签名，30分钟后过期
     *
     * @author chenlirun
     * @date 2021/7/8 17:52
     */
    public static String sign(Long id){
        Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);
        // 设置jwt 令牌，Algorithm.HMAC256() 加密令牌
        Algorithm algorithm = Algorithm.HMAC256(TokenLoginInfo.secret);
        return JWT.create().withClaim("id",id) // 设置属性，可以是当前登录的用户信息，给 Payload
                .withExpiresAt(date) // 设置过期时间
                .withIssuedAt(new Date()) // 设置当前创建时间
                .sign(algorithm); // 签发一个新的jwt 令牌
    }

    /**
     * 校验 token
     * @author chenlirun
     * @date 2021/7/8 17:56
     */
    public static boolean verify(String token,Long id,String secret){
        try {
            Algorithm algorithm = Algorithm.HMAC256(secret);
            /**
             * JWT.require(): 验证签名的算法
             * .build(): 使用已经提供的配置创建一个新的可重用的JWTVerifier实例。
             *          返回:一个新的jwtverification实例。
              */
            JWTVerifier verifier = JWT.require(algorithm).withClaim("id", id).build();
            verifier.verify(token);
            return true;
        }catch (Exception e){
            return false;
        }

    }

    /**
     * 解析token，id
     * @author chenlirun
     * @date 2021/7/9 11:20
     */
    public static Long getTokenById(HttpServletRequest request){
        String token = request.getHeader("token");
        /**
         * 解码给定的Json Web令牌。
         * 注意，这个方法并不验证令牌的签名!只有在您信任令牌或您已经验证了它时才使用它。
         * 返回:解码后的jwt。抛出:JWTDecodeException
         * -如果令牌的任何部分包含一个无效的jwt或每个jwt部分的JSON格式。
         */
        DecodedJWT decode = JWT.decode(token);
        return decode.getClaim("id").asLong();
    }

    /**
     * 认证token
     * @author chenlirun
     * @date 2021/7/16 14:56
     */
    public static boolean validToken(HttpServletRequest request, UserLoginPermission tag){
        String token = request.getHeader("token");
        if(token==null){
            throw new BaseException(BaseCodeResult.ERROR,"当前未登录");
        }
        Long userId = UserLoginTokenUtil.getTokenById(request);
        // 验证 token 签名
        System.out.println(TokenLoginInfo.secret);
        boolean verifySuccess = UserLoginTokenUtil.verify(token, userId, TokenLoginInfo.secret);
        if(!verifySuccess){
            throw new BaseException(BaseCodeResult.ERROR,"token 签名不正确");
        }
        // 验证访问权限
        UserRole userRole = userLoginTokenUtil.userRoleMapper.selectByUserId(userId);
        Byte roleId = userRole.getRoleId();
        if (ObjectUtil.isEmpty(userRole)){
            throw new BaseException(BaseCodeResult.ERROR,"该用户无可用权限");
        }
        /**
         * String.indexOf()
         * 返回此字符串中指定子字符串的第一个匹配项的索引。
         * 返回的索引是k中最小的值:这.开始与（斯特，k）如果kexists没有这个值，则返回-1。
         * str -要搜索的子字符串。
         * 返回:指定子字符串第一个匹配项的索引，如果没有匹配项则返回-1。
         */
        int i = tag.role().indexOf(String.valueOf(roleId));
        if(i == -1){
            throw new BaseException(BaseCodeResult.ERROR,"无访问权限");
        }
        return true;
    }
}
